Skip to content

Resolve DQ04: a Plan's identity is its origin - #2

Merged
schickling merged 8 commits into
mainfrom
dq04-plan-identity
Jul 27, 2026
Merged

Resolve DQ04: a Plan's identity is its origin#2
schickling merged 8 commits into
mainfrom
dq04-plan-identity

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Resolves DQ04 — a Plan's identity, now that minting is gone and it cannot be the path.

Decision (0017)

A Plan's identity (PlanRef) is the content hash of its origin — the one version with no predecessor. Fully derived, encodes no location, collision-free; two versions are the same Plan iff they share an origin, and the origin's own hash is the PlanRef. The required goal is promoted to the human handle, so identity stays purely machine-facing and never has to be readable.

This fixes a real hole: the implementation used the catalog directory as the Plan's handle, making identity a filesystem location (which the PlanRef ontology entry forbids) — so a moved or misfiled version silently changed which Plan it belonged to.

What changed

  • derive_planref: an origin is its own PlanRef; a revision walks its predecessor imports to the origin and hashes that. The operator names nothing.
  • start takes no identifier (only --goal), scaffolds into a drafts/ staging area; commit derives the PlanRef and files under plans/<planref>/.
  • Admission rejects a misfiled Plan — a version whose derived origin ≠ its directory is Rejected, never reinterpreted (an absent ancestor is an orphan, left alone).
  • goal is required (validated on the evaluated value) and is surfaced by status/show/history/progress/evidence in place of the hash; reads resolve a Plan by PlanRef, unique hash-prefix, or unique goal.
  • Examples re-filed under their origin hashes via git mv — version bytes and filenames unchanged, so the filename-reproduces-from-bytes oracle holds; READMEs refer to each plan by its goal.

Verification

cargo build --release, clippy --all-targets -- -D warnings, fmt --check clean; cargo test 130 lib + 15 acceptance; nix build .#compass passes; axe vrs check --profile strict green.

Also in this PR

  • Fixes a duplicate-requirement-id bug the DQ04 edit introduced (new CMP.DM-R11/R12 collided with the Progress/acceptance block) by renumbering the new pair to R17/R18. Note: axe vrs check did not catch the duplicate ids — a checker gap, flagged separately.
  • Corrects 0017's CMP.DM-R08 mis-citation and the stale pl_-named cross-plan example.

Known, out of scope

  • Migration: an existing pre-0017 catalog with pl_* dirs would have every plan rejected-as-misfiled. Correct per 0017; acceptable at v0 (no users), no migration path provided (fresh-namespace).
  • Cross-plan dependency edges remain unsupported (DQ11); this PR does not touch that.

Draft for review of the identity model before it lands on main.

Also: identity vocabulary cleanup (revisiting the ontology)

The *Ref terms were coined for the minted-reference design (opaque tokens). That design is gone — a Step's identity is its declared name (0012), a Plan's is its origin hash (0017). So:

  • StepRef is dropped as a standalone term (no ref artifact exists; a dependency names the declaration), folded into the Step ontology entry.
  • PlanRef → PlanId — an identity, not a pointer.

Propagated across specs/api/cli/examples/code (derive_planrefderive_planid); the superseded decision 0004 keeps the old terms as a historical record, and the ontology _Avoid_ lists them so old records map cleanly.

schickling-assistant and others added 8 commits July 27, 2026 11:36
decision 0017: PlanRef = content hash of the origin (predecessor-less)
version. Fully derived (CMP-R10), encodes no location (CMP.DM-R08), cannot
collide, and makes 'same Plan' a content fact — two versions share a Plan iff
they share an origin, and the origin's own identity is the PlanRef.

The required goal becomes the human handle (CMP.DM-R12), so identity need not
be readable and stays purely derived — machine identity and human readability
each keep their own job. A Plan is never renamed because it was never named.

Fixes the real hole: the impl used the catalog directory as the handle, making
identity a path (violating CMP.DM-R08); a misfiled version now resolves its
Plan from its origin and is rejected on mismatch, not reinterpreted. The
CMP-R11 objection (a hash is unavailable before the origin exists) does not
bind: authoring references no PlanRef, so starting stays one command.

Updates ontology, 01-data-model (CMP.DM-R11/R12), 02-artifacts (dir is the
PlanRef), 06-api, 04-cli; DQ04 marked resolved.
Replace the operator-chosen catalog directory name with a derived PlanRef: the
content hash of a Plan's origin — the single predecessor-less version. Identity
is derived, never chosen, never minted, and encodes no location.

- catalog: derive_planref walks predecessor imports to the origin and hashes it;
  an origin is its own PlanRef (its version-id and the PlanRef are the same hash).
  load_plan rejects a version whose derived origin != the dir it is filed under,
  on the same terms as a content-hash-vs-filename mismatch — never reinterpreted.
- cli/cmd: start names no plan and scaffolds into a drafts/ staging area with a
  blank (required) goal; commit derives the PlanRef and files under it — --plan
  and <plan> args removed. Reads address a Plan by PlanRef (the dir), a unique
  hash prefix, or, as a nicety, an unambiguous goal.
- model: a non-empty goal is required on every version (CMP.DM-R12), checked on
  the evaluated value so revisions inheriting their goal pass.
- display: status and history lead with the goal (the human handle), not the hash.
- examples: re-file the three plans under their origin hashes (bytes unchanged);
  READMEs refer to each plan by its goal and note the PlanRef is the origin hash.
- tests: cover origin-files-under-own-hash, revision-shares-PlanRef, misfiled
  rejection, empty-goal refusal, and goal display; rewrite the e2e + cross-plan
  suites for the derived-identity, no-name-arg flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete decision 0017's "goal is the human handle, surfaced wherever a Plan is
referenced": the progress and evidence confirmation lines printed the raw
PlanRef; show the goal instead (the version is still cited by its hash). Add an
acceptance test asserting both commands render the goal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mple

The DQ04 edit added CMP.DM-R11/R12 in the Identity section, colliding with
the pre-existing Progress/acceptance R11/R12 — a duplicate-id bug that
axe vrs check did not catch. Renumber the new pair to R17 (Plan identity)
and R18 (goal), leaving the older Progress/Acceptance ids and the
07-evals CMP.DM-R12 citation valid. Update the impl comments that cited
the new ids.

Also: decision 0017 cited CMP.DM-R08 (Step identity = declared name) as
forbidding location-as-identity; the no-location property is the PlanRef
ontology entry — corrected. And the 06-api cross-plan example showed a
pl_-named dir; under 0017 a plan segment is the origin hash, so the
example now uses one and refers to the other plan by its goal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
The *Ref terms were coined for the minted-reference design, where identities
were opaque tokens you passed around. That design is gone: a Step's identity
is its declared export name (0012) and a Plan's is its origin hash (0017) —
intrinsic identities, not reference tokens.

- StepRef is dropped as a standalone term. There is no ref artifact: a Step's
  identity is its name, and depending on a Step names the declaration (a
  language reference). Folded into the Step ontology entry.
- PlanRef -> PlanId: it is an identity (a derived hash), not a pointer to
  elsewhere; a cross-Plan reference imports the other version.

Propagated across the current layer (specs, api, cli, examples, code incl.
derive_planref -> derive_planid). The superseded decision 0004 keeps the old
terms as a historical record; the ontology's _Avoid_ lists PlanRef/StepRef so
a reader of old records maps them. 130 lib + 15 acceptance, clippy/fmt/vrs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
The 22-term list was flat. Group under six headings so the structure is
legible without reading every entry: the tool; intent (Plan, PlanId,
Version, Revision, Rationale, Step); lineage and its states (Head,
Divergence, Reconciliation, Orphan, Unresolved); reading, storage, and
replication; execution record; change surface and composition. Term text
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…name predecessor→parent

Interview-resolved ontology-refinement pass:

- Surface cluster: drop Plan Surface, Receipt, Observation as terms. Anchor on
  Commit as the single write act; the write surface has two acts (Commit →
  Plan Version, Progress Event → append), not one Mutation umbrella. The
  Plan Surface invariant survives as a property; Receipt's role is the version
  hash itself; Observation's guarantee relocates to 05-integrations.
- Add Goal, Acceptance, Evidence as terms. Strike phantom Gate — the acceptance
  predicate is the gate.
- Rename predecessor → parent (aligns with the `parents` the impl already emits).
- Split lineage terms: "Lineage shapes" (Head, Divergence, Reconciliation) vs
  "Incomplete replication" (Orphan, Unresolved).
- Rationale keeps its term, bridged explicitly to the `why` field.

Mutation→Commit and Receipt→Plan Version propagated across specs/requirements
and the governing 0001 decision; CMP.SURF-R03 rescoped to the two-act model.
Decisions and frozen example modules keep their period vocabulary.

axe vrs check --profile strict: ok

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename predecessor → parent across src and tests (helper fns, error strings,
  doc-comments, test names). The JSON already emitted `parents`; this closes the
  gap between the code's internal words and what it produces.
- Rename receipt_json → commit_result_json: the output has no `receipt` key, and
  the ontology no longer defines Receipt — the returned value is the Plan Version.
- Rewrite the readiness "On gates" note: the spec no longer says "dependencies
  and gates"; the ontology states the acceptance predicate *is* the gate.

Frozen example modules under catalog/plans/*/versions/*.ts are untouched — they
keep their period vocabulary, and the acceptance hash oracle
(example_filenames_reproduce_from_source_bytes) confirms none drifted.

cargo test: 15 passed. clippy + fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@schickling
schickling marked this pull request as ready for review July 27, 2026 14:05
@schickling
schickling merged commit b9467f8 into main Jul 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants